#include "SoundObj.hpp"

SoundObj::SoundObj(const char* FILE)
{
	this->InitEqualizer();
	Read(FILE);
}

SoundObj::SoundObj(bool empty)
{
	this->InitEqualizer();
	empty ? this->NbrOfChannels = 0  : SoundObj();
}

SoundObj::SoundObj(long signal_type , long signal_freq , long signal_size, long scale , long sampling)
{
	this->InitEqualizer();
	double v=0;
	/* Constructor to create a new sound */
	FileTypeBlockID = new char[5];
	FileTypeBlockID[4] = '\0';
	FileFormatID = new char[5];
	FileFormatID[4] = '\0';
	FormatBlockID = new char[5];
	FormatBlockID[4] = '\0';
	DataBlockID = new char[5];
	DataBlockID[4] = '\0';

	this->FileTypeBlockID = "RIFF\0";
	this->BlockSize = 16;
	this->FileSize = 40 + sampling*16*signal_size ;
	this->FileFormatID = "WAVE\0" ;
	this->FormatBlockID= "fmt \0";
	this->AudioFormat = 1;
	this->NbrOfChannels = 1;
	this->Frequency = sampling;
	this->BytePerSec = sampling*2;
	this->BytePerBlock = 2;
	this->BitsPerSample = 16;
	this->DataBlockID = "data\0";
	this->DataSize = 2*Frequency*signal_size;
	this->SampleSize = BytePerBlock / NbrOfChannels;

	ChannelSize = this->DataSize / this->NbrOfChannels / this->SampleSize;

	DATA = new long*[this->NbrOfChannels];
	for (long i=0; i< this->NbrOfChannels; i++) {
		DATA[i] = new long[ChannelSize];
	}
	for (long j=0; j < ChannelSize ; j++) {
		for (long k=0; k< this->NbrOfChannels; k++) {
			if (signal_type==0) { /* sin */
				DATA[k][j] = (long)(scale * sin(PI*(2*signal_freq*(double)j/(double)sampling)));

			} else if (signal_type==1) { /* square */
				v = sin(2*PI*signal_freq*(double)j/(double)sampling);
				DATA[k][j] = (long)(scale * ((v > 0) - (v < 0)));

			} else if (signal_type==2) { /* triangle */
				DATA[k][j] = (long)(scale * (2 * fabs( signal_freq*(double)j/(double)sampling - 2 * floor( (signal_freq*(double)j/(double)sampling) / 2 ) - 1 ) - 1));

			} else if (signal_type==3) { /* positive sin */
				v = (scale * sin(PI*(2*signal_freq*(double)j/(double)sampling)));
				DATA[k][j] = (long)(((v > 0) - (v < 0))*v);
			}
		}
	}
}
void SoundObj::Write(const char* FILE)
{
	ofstream writtenFile;
	writtenFile.open(FILE, ios::out | ios::binary);

	if (writtenFile.is_open())
	{
		writtenFile.write (this->FileTypeBlockID, 4);
		this->DecimalToBin(writtenFile, 4, this->FileSize);
		writtenFile.write (this->FileFormatID, 4);
		writtenFile.write (this->FormatBlockID, 4);
		this->DecimalToBin(writtenFile, 4, this->BlockSize);
		this->DecimalToBin(writtenFile, 2, this->AudioFormat);
		this->DecimalToBin(writtenFile, 2, this->NbrOfChannels);
		this->DecimalToBin(writtenFile, 4, this->Frequency);
		this->DecimalToBin(writtenFile, 4, this->BytePerSec);
		this->DecimalToBin(writtenFile, 2, this->BytePerBlock);
		this->DecimalToBin(writtenFile, 2, this->BitsPerSample);

		writtenFile.write (this->DataBlockID, 4);
		this->DecimalToBin(writtenFile, 4, this->DataSize);
		//cout << " MAX: " << ChannelSize << endl;
		for (long i=0; i < this->ChannelSize ; i++)
		{
			for (long j=0; j< this->NbrOfChannels; j++) {
				//    cout << i << ": " << DATA[j][i] << endl;
				this->DecimalToBin(writtenFile,this->SampleSize, this->DATA[j][i]);
			}
		}
		writtenFile.close();
		// cout << "Finished writing file" << endl;
	}
	else
		cout << "Can't open file " << FILE << endl;
}

void SoundObj::Read(const char* FILE)
{
	ifstream soundfile;

	FileTypeBlockID = new char[5];
	FileTypeBlockID[4] = '\0';
	FileFormatID = new char[5];
	FileFormatID[4] = '\0';
	FormatBlockID = new char[5];
	FormatBlockID[4] = '\0';
	DataBlockID = new char[5];
	DataBlockID[4] = '\0';

	soundfile.open(FILE, ios::in | ios::binary);

	if (soundfile.is_open())
	{
		soundfile.read(this->FileTypeBlockID, 4);
		this->FileSize = BinToDecimal(soundfile, 4);
		soundfile.read(this->FileFormatID, 4);
		soundfile.read(this->FormatBlockID, 4);

		if (strcmp(this->FileTypeBlockID, "RIFF") == 0
				|| strcmp(this->FileFormatID, "WAVE") == 0
				|| strcmp(this->FormatBlockID, "fmt ") == 0)
		{
			//cout << "Opened sound file" << endl;
			this->BlockSize = BinToDecimal(soundfile, 4);

			this->AudioFormat = BinToDecimal(soundfile, 2);

			if (this->AudioFormat != 1) throw 2;

			this->NbrOfChannels = BinToDecimal(soundfile, 2);
			this->Frequency = BinToDecimal(soundfile, 4);
			this->BytePerSec = BinToDecimal(soundfile, 4);
			this->BytePerBlock = BinToDecimal(soundfile, 2);
			this->BitsPerSample = BinToDecimal(soundfile, 2);
			soundfile.read(this->DataBlockID, 4);
			this->DataSize = BinToDecimal(soundfile, 4);
			this->SampleSize = BytePerBlock / NbrOfChannels;
			this->ChannelSize = this->DataSize / this->NbrOfChannels / this->SampleSize;
			DATA = new long*[this->NbrOfChannels];

			for (long i=0; i< this->NbrOfChannels; i++)
				DATA[i] = new long[this->ChannelSize];

			for (long j=0; j < this->ChannelSize ; j++)
			{
				for (long k=0; k< this->NbrOfChannels; k++)
					DATA[k][j] = BinToDecimal(soundfile,this->SampleSize);
			}
			soundfile.close();

	/*		cout << "Opened sound file" << endl;
			cout << "FileTypeBlockID: "<< this->FileTypeBlockID << endl;
			cout << "FileSize: "<< this->FileSize << endl;
			cout << "FileFormatID: "<< this->FileFormatID << endl;
			cout << "FormatBlockID: "<< this->FormatBlockID << endl;
			cout << "BlockSize: "<< this->BlockSize << endl;
			cout << "AudioFormat: "<< this->AudioFormat << endl;
			cout << "NbrOfChannels: "<< this->NbrOfChannels << endl;
			cout << "Frequency: "<< this->Frequency << endl;
			cout << "BytePerSec: "<< this->BytePerSec << endl;
			cout << "BytePerBlock: "<< this->BytePerBlock << endl;
			cout << "BitsPerSample: "<< this->BitsPerSample << endl;
			cout << "DataBlockID: "<< this->DataBlockID << endl;
			cout << "DataSize: "<< this->DataSize << endl;
			cout << "SampleSize: "<< this->SampleSize << endl;
			cout << "ChannelSize: "<< this->ChannelSize << endl;
			cout << "Finished opening file" << endl;
*/
		}
		else
		{
			throw 0;
		}
	}
	else
	{
		throw 1;
		cout << "Can't open file " << FILE << endl;
	}
}

long SoundObj::BinToDecimal(ifstream& file, const long dataSize) const
{
	unsigned char* dataChunk = new unsigned char[dataSize];
	file.read((char*)dataChunk, dataSize);
	long value = 0;
	if(dataChunk[dataSize-1]&0x80)
		value=-256;


	for (long i=dataSize-1; i>=0; i--)
	{
		//last 8 bits
		value |= (dataChunk[i]);
		if (i > 0)
			value <<= 8;
	}
	delete[] dataChunk;
	return value;
}

void SoundObj::DecimalToBin(ofstream& file, const long dataSize, long value) const
{
	char* dataChunk = new char[dataSize];
	//little indian
	for (long i=0; i< dataSize; i++)
	{
		dataChunk[i] = (value & 255);
		value >>= 8;
	}
	file.write(dataChunk, dataSize);
	delete[] dataChunk;
}


SoundObj::~SoundObj()
{
	if (DATA != NULL)
	{
		for (long i=0; i< NbrOfChannels; i++)
			delete[] DATA[i];
		delete[] DATA;
	}
}

/**
 * change the current soundObj sampleSize
 * @param newSampleSize
 * @return 0 if success
 */
long SoundObj::setSampleSize(long newSampleSize)
{
	long max,newMax,i,j;
	double bitFactor,sizeFactor;

	if(newSampleSize==this->SampleSize)
		return 0;

	// max value
	max=(long)(powl(2,this->SampleSize*8)/2);
	newMax=(long)(powl(2,newSampleSize*8)/2);

	// current object unsigned -> signed
	if((this->SampleSize==1) && (newSampleSize>1))
	{
		i=0;
		while(i<this->ChannelSize)
		{
			j=0;
			while(j<this->NbrOfChannels)
				this->DATA[j++][i]-=127;
			++i;
		}
	}

	bitFactor=(double)newMax/(double)max;
	sizeFactor=(double)newSampleSize/(double)this->SampleSize;

	i=0;
	while(i<this->ChannelSize)
	{
		j=0;
		while(j<this->NbrOfChannels)
		{
			this->DATA[j][i]=(long)((double)this->DATA[j][i]*bitFactor);
			++j;
		}
		++i;
	}
	// current object signed -> unsigned
	if((this->SampleSize>1) && (newSampleSize==1))
	{
		i=0;
		while(i<this->ChannelSize)
		{
			j=0;
			while(j<this->NbrOfChannels)
				this->DATA[j++][i]+=127;
			++i;
		}
	}
	this->SampleSize=(long)(sizeFactor*(double)this->SampleSize);
	this->BitsPerSample=(long)(sizeFactor*(double)this->BitsPerSample);
	this->BytePerSec=(long)(sizeFactor*(double)this->BytePerSec);
	this->BytePerBlock=(long)(sizeFactor*(double)this->BytePerBlock);
	this->FileSize=(long)(((FileSize-40)*(double)sizeFactor)+40);
	return 0;
}

complex<double>* SoundObj::computeFFT(long channel)
{
	complex<double>* data = new complex<double> [this->ChannelSize];

	for (long i=0; i<this->ChannelSize; i++)
	{
		data[i] = complex <double>(this->DATA[channel-1][i]);
	}

	complex<double> * fft = new complex<double> [this->ChannelSize];

	complex<double> * f = new complex<double> [FFT_WIDTH];

	long size = this->ChannelSize - this->ChannelSize%FFT_WIDTH;

	for (long i=0; i<size; i+=FFT_WIDTH)
	{
		for (long j=0; j<FFT_WIDTH; j++)
		{
			f[j] = data[i+j];
		}

		f = this->FFT(f, FFT_WIDTH);

		for (long j=i; j<i+FFT_WIDTH; j++)
		{
			fft[j] = f[i%FFT_WIDTH];
		}
	}

	f = new complex<double> [this->ChannelSize%FFT_WIDTH];

	for (long i=size; i<this->ChannelSize; i++)
	{
		f[i-size] = data[i];
	}

	f = this->FFT(f, this->ChannelSize%FFT_WIDTH);

	for (long i=size; i<this->ChannelSize; i++)
	{
		fft[i] = f[i-size];
	}

	return fft;

	//return this->FFT(data, n);
}

complex<double>* SoundObj::computeIFFT(long channel)
{
	return this->FFT(computeFFT(channel), this->ChannelSize);
}

complex<double>* SoundObj::IFFT(const complex<double>* data, long n)
{
	return this->FFT(FFT(data,n), n);
}

complex<double>* SoundObj::FFT(const complex<double> *data, const long n)
{
	complex<double> *fft = new complex<double>[n];

	long M = (long)floor(n/2.0);

	for (long k=0; k<M; ++k)
	{
		fft[k]   = Sum(data, n, k, 0) + polar(1., -2*k*PI/n) * Sum(data, n, k, 1);
		fft[k+M] = Sum(data, n, k, 0) - polar(1., -2*k*PI/n) * Sum(data, n, k, 1);
	}

	return fft;
}

complex<double> SoundObj::Sum(const complex<double> *data, const long n, const long k, const long even)
{
	complex<double> res;

	for (long m=0; m<floor(n/2.0); ++m)
		res += data[2*m+even]*polar(1., -2*PI*2*m*k/n);

	return res;
}

void SoundObj::InitEqualizer()
{
	double equalizer[21] = {2,1,5,1,9,1,19,1,38,1,75,1,150,1,300,1,600,1,1200,1,2000};

	this->Equalizer = new double[21];
	for (long i=0; i<21; i++)
		this->Equalizer[i] = equalizer[i];
}

void SoundObj::updateEqualizer(long id, long val)
{
	this->Equalizer[id] = val;
}

complex<double> * SoundObj::Equalize(complex<double> *fft)
{
	for (long i=1; i<20; i+=2)
	{
		for(long j=this->Equalizer[i-1]; this->Equalizer[i]!=1 && j<this->Equalizer[i+1]; j++)
		{
			fft[j] = real(fft[j]) +imag(fft[j]) * this->Equalizer[i];
		}
	}
	return fft;
}

long SoundObj::getDataByBits(long channel, long bit)
{
	if (channel > this->NbrOfChannels) return 0;
	if (bit > this->ChannelSize) return 0;

	return this->DATA[channel-1][bit];
}

long* SoundObj::getDataByChannel(long channel)
{
	return (channel > this->NbrOfChannels) ? ((long *) 0) : this->DATA[channel-1];
}

long** SoundObj::getData() { return this->DATA;}
long SoundObj::getNbrOfChannels() { return this->NbrOfChannels;}
long SoundObj::getDataSize() { return this->ChannelSize;}

long SoundObj::getBytePerSec() {return this->BytePerSec;}

long SoundObj::getSoundLength()
{
	return (long) ceil(this->ChannelSize/(float)this->Frequency);
}

long SoundObj::getAudioFormat()
{
	return AudioFormat;
}

double * SoundObj::getEqualizer()
{
	if (this->Equalizer == NULL) this->InitEqualizer();
	return this->Equalizer;
}

SoundObj * SoundObj::Concat(SoundObj * sound)
{
	if (this->Frequency !=sound->Frequency || this->BytePerSec != sound->BytePerSec) throw 4;

	SoundObj * Res = new SoundObj();

	Res->NbrOfChannels   = max(sound->NbrOfChannels, this->NbrOfChannels);
	Res->FileTypeBlockID = "RIFF\0";
	Res->BlockSize       = this->BlockSize;
	Res->FileSize        = sound->FileSize + this->FileSize;
	Res->FileFormatID    = this->FileFormatID ;
	Res->FormatBlockID   = this->FormatBlockID;
	Res->AudioFormat     = this->AudioFormat;
	Res->BytePerSec      = this->BytePerSec;
	Res->BytePerBlock    = this->BytePerBlock;
	Res->BitsPerSample   = this->BitsPerSample;
	Res->DataBlockID     = this->DataBlockID;
	Res->DataSize        = sound->DataSize + this->DataSize;
	Res->SampleSize      = Res->BytePerBlock / Res->NbrOfChannels;
	Res->ChannelSize     = this->ChannelSize + sound->ChannelSize;
	Res->Frequency       = this->Frequency;

	Res->DATA = new long * [Res->NbrOfChannels];

	for (long i=0; i< Res->NbrOfChannels; i++)
	{
		Res->DATA[i] = new long[Res->ChannelSize];
	}

	for (long i=0; i < Res->NbrOfChannels; i++)
	{
		for (long j=0; j < Res->ChannelSize; j++)
		{
			Res->DATA[i][j] = 0;
		}
	}

	for (long k=0; k<Res->NbrOfChannels; k++)
	{
		for (long j=0; j < sound->ChannelSize ; j++)
		{
			Res->DATA[k][j] = sound->DATA[k][j];
		}
	}

	for (long k=0; k<Res->NbrOfChannels; k++)
	{
		for (long j=sound->ChannelSize; j < Res->ChannelSize ; j++)
		{
			Res->DATA[k][j] = this->DATA[k][j-sound->ChannelSize];
		}
	}

	return Res;
}

/**
 * mix & normalize
 */
long SoundObj::mixWith(SoundObj*son,long mod)
{
	/* mod=1 : simple addition */
	/* mod=2 : mean */
	/* mod=3 : normalize */
	if(this->Frequency!=son->Frequency)
		throw -1;

	if(this->SampleSize!=son->SampleSize)
		throw -2;

	if(this->NbrOfChannels!=son->NbrOfChannels)
		throw -3;

	/* meme nombre de channels : ok */
	long i=0,j;
	long valMax=0;

	/* take the longest */
	long ChannelSize=(son->ChannelSize>this->ChannelSize)?son->ChannelSize:this->ChannelSize;

	long** DATA=(long**)malloc(sizeof(long*)*son->NbrOfChannels);

	i=0;
	while(i<son->NbrOfChannels)
		DATA[i++]=(long*)malloc(sizeof(long)*ChannelSize);

	long cas=1;
	if(son->ChannelSize>this->ChannelSize)
		cas=2;

	/* son a ajouter plus court que son d'origine : ok */
	j=0;
	while(j<son->NbrOfChannels)
	{
		i=0;
		if(cas==2)
		{
			while(i<this->ChannelSize)
			{
				DATA[j][i]=(this->DATA[j][i]+son->DATA[j][i]);
				if(valMax<DATA[j][i])
					valMax=DATA[j][i];
				++i;
			}
			while(i<son->ChannelSize)
			{
				DATA[j][i]=son->DATA[j][i];
				if(valMax<DATA[j][i])
					valMax=DATA[j][i];
				++i;
			}
			++j;
		}
		else
		{
			while(i<son->ChannelSize)
			{
				DATA[j][i]=(this->DATA[j][i]+son->DATA[j][i]);
				if(valMax<DATA[j][i])
					valMax=DATA[j][i];
				++i;
			}
			while(i<this->ChannelSize)
			{
				DATA[j][i]=this->DATA[j][i];
				if(valMax<DATA[j][i])
					valMax=DATA[j][i];
				++i;
			}
			++j;
		}
	}

	long realMax=(long)(powl(2,this->SampleSize*8)/2);
	if(this->SampleSize==1)
		realMax*=2;


	/* normalize */
	if(mod==3)
	{
		double ratio=(double)realMax/(double)valMax;

		i=0;
		while(i<son->ChannelSize)
		{
			j=0;
			while(j<son->NbrOfChannels)
			{
				DATA[j][i]=(long)((double)DATA[j][i]*ratio);
				++j;
			}
			++i;
		}
	}

	/* mean */
	if(mod==2)
	{
		i=0;
		while(i<son->ChannelSize)
		{
			j=0;
			while(j<son->NbrOfChannels)
			{
				DATA[j][i]/=2;
				++j;
			}
			++i;
		}
	}

	/* addition */
	if(mod==1)
	{
		i=0;
		while(i<son->ChannelSize)
		{
			j=0;
			while(j<son->NbrOfChannels)
			{
				DATA[j][i]=(DATA[j][i]>realMax)?realMax:DATA[j][i];
				++j;
			}
			++i;
		}
	}

	if(son->ChannelSize>this->ChannelSize)
		this->ChannelSize=son->ChannelSize;

	delete[] this->DATA;
	this->DATA=DATA;

	return 0;
}
